#include "preHeader.h"
#include "QuestMgr.h"
#include "Map/MapInstance.h"

inline bool IsCareerMatchable(Player* pPlayer, uint32 limitCareer) {
	return limitCareer == 0 || limitCareer == pPlayer->GetCareer();
}
inline bool IsGenderMatchable(Player* pPlayer, uint32 limitGender) {
	return limitGender == 0 || limitGender == pPlayer->GetGender();
}
template <typename T>
bool IsCareerGenderMatchable(Player* pPlayer, const T& limitCfg) {
	return IsCareerMatchable(pPlayer, limitCfg.onlyCareer) &&
		IsGenderMatchable(pPlayer, limitCfg.onlyGender);
}

inline bool IsReqQuestsReached(
	const QuestPrototype* pQuestProto, QuestStorage* pQuestStorage) {
	if (pQuestProto->questFlags.isAnyReqQuest) {
		for (auto questReqQuest : pQuestProto->questReqQuests) {
			if (pQuestStorage->IsQuestHasFinished(questReqQuest)) {
				return true;
			}
		}
		return pQuestProto->questReqQuests.empty();
	} else {
		for (auto questReqQuest : pQuestProto->questReqQuests) {
			if (!pQuestStorage->IsQuestHasFinished(questReqQuest)) {
				return false;
			}
		}
		return true;
	}
}

QuestMgr::QuestMgr()
{
}

QuestMgr::~QuestMgr()
{
}

bool QuestMgr::LoadQuests()
{
	auto pQuestTbl = sDBMgr.GetTable<QuestPrototype>();
	for (auto& pair : MakeIteratorRange(pQuestTbl->Begin(), pQuestTbl->End())) {
		const QuestPrototype* pQuestProto = &pair.second;
		if (pQuestProto->questFlags.isWatchStatus) {
			m_allWatchStatusQuests.push_back(pQuestProto);
			if (pQuestProto->questReqMinLv != 0 || pQuestProto->questReqMaxLv != 0) {
				m_allWatchStatusQuests4LvReq.push_back(pQuestProto);
			}
			for (auto questTypeID : pQuestProto->questReqQuests) {
				m_allWatchStatusQuests4QuestReq[questTypeID].push_back(pQuestProto);
			}
			for (auto& chequeReq : pQuestProto->questReqCheques) {
				m_allWatchStatusQuests4ChequeReq[chequeReq.chequeType].push_back(pQuestProto);
			}
			for (auto& itemReq : pQuestProto->questReqItems) {
				m_allWatchStatusQuests4ItemReq[itemReq.itemTypeID].push_back(pQuestProto);
			}
		} else {
			if (pQuestProto->questPublisher.objType != (u32)QuestObjType::None &&
				pQuestProto->questPublisher.objID != 0) {
				m_allObjCareNoWatchStatusQuests[pQuestProto->questPublisher.objType]
					[pQuestProto->questPublisher.objID].push_back(pQuestProto);
			}
		}
	}
	return true;
}

GErrorCode QuestMgr::HandleAcceptQuest(Player* pPlayer, LocatableObject* pLObj, const QuestPrototype* pQuestProto, QuestLog** pQuestLogPtr)
{
	if (!pPlayer->IsInWorld() || pQuestProto == NULL) {
		return InvalidRequest;
	}
	auto errCode = IsQuestObjInstMatchable(pPlayer, pLObj, pQuestProto->questPublisher);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
	if (questStatus != QMGR_QUEST_AVAILABLE) {
		return InvalidRequest;
	}

	CostQuestReqCheques(pPlayer, pQuestProto, CFT_QUEST_ACCEPT);
	DestroyQuestReqItems(pPlayer, pQuestProto, IFT_QUEST_ACCEPT);
	if (pQuestProto->questReqExtra.scriptID != 0) {
		AcceptQuest4Script(pPlayer, pQuestProto);
	}

	auto pQuestLog = new QuestLog(pPlayer);
	pQuestLog->Init(pQuestProto);
	pPlayer->GetQuestStorage()->AddQuestLog(pQuestLog);

	ReceiveQuestInitCheques(pPlayer, pQuestProto, CFT_QUEST_ACCEPT);
	ReceiveQuestInitItems(pPlayer, pQuestProto, IFT_QUEST_ACCEPT);
	if (pQuestProto->questInitExtra.scriptID != 0) {
		RunScriptFileById(pPlayer->GetMapInstance()->L,
			pQuestProto->questInitExtra.scriptID, pPlayer, pQuestProto,
			std::string_view(pQuestProto->questInitExtra.scriptArgs));
	}

	OnQuestAccept(pPlayer, pQuestLog);
	if (pQuestLogPtr != NULL) {
		*pQuestLogPtr = pQuestLog;
	}

	RefreshQuestWatchStatus(pPlayer, pQuestProto->questTypeID);
	pQuestLog->RefreshQuestFinishStatus();

	return CommonSuccess;
}

GErrorCode QuestMgr::HandleCancelQuest(Player* pPlayer, LocatableObject* pLObj, uint32 questUniqueKey)
{
	if (!pPlayer->IsInWorld()) {
		return InvalidRequest;
	}
	auto pQuestStorage = pPlayer->GetQuestStorage();
	auto pQuestLog = pQuestStorage->GetQuestLogByKey(questUniqueKey);
	if (pQuestLog == NULL) {
		return InvalidRequest;
	}
	auto pQuestProto = pQuestLog->GetQuestProto();
	if (pQuestProto->questFlags.isCantCancel) {
		return InvalidRequest;
	}

	OnQuestCancel(pPlayer, pQuestLog);
	pQuestStorage->RemoveQuestLog(pQuestLog);
	auto questTypeID = pQuestProto->questTypeID;
	delete pQuestLog;

	RefreshQuestWatchStatus(pPlayer, questTypeID);

	return CommonSuccess;
}

GErrorCode QuestMgr::HandleSubmitQuest(Player* pPlayer, LocatableObject* pLObj, uint32 questUniqueKey)
{
	if (!pPlayer->IsInWorld()) {
		return InvalidRequest;
	}
	auto pQuestStorage = pPlayer->GetQuestStorage();
	auto pQuestLog = pQuestStorage->GetQuestLogByKey(questUniqueKey);
	if (pQuestLog == NULL) {
		return InvalidRequest;
	}
	if (!pQuestLog->IsQuestFinished()) {
		return InvalidRequest;
	}
	auto pQuestProto = pQuestLog->GetQuestProto();
	auto errCode = IsQuestObjInstMatchable(pPlayer, pLObj, pQuestProto->questPublisher);
	if (errCode != CommonSuccess) {
		return errCode;
	}

	pQuestLog->SetQuestSubmitting();
	pQuestLog->CleanResource4SubmitQuestAction();
	RetrieveQuestInitCheques(pPlayer, pQuestProto, CFT_QUEST_SUBMIT);
	RetrieveQuestInitItems(pPlayer, pQuestProto, IFT_QUEST_SUBMIT);
	RefundQuestReqCheques(pPlayer, pQuestProto, CFT_QUEST_SUBMIT);
	RefundQuestReqItems(pPlayer, pQuestProto, IFT_QUEST_SUBMIT);
	ReceiveQuestRewardCheques(pPlayer, pQuestProto, CFT_QUEST_SUBMIT);
	ReceiveQuestRewardItems(pPlayer, pQuestProto, IFT_QUEST_SUBMIT);
	if (pQuestProto->questRewardExtra.scriptID != 0) {
		RunScriptFileById(pPlayer->GetMapInstance()->L,
			pQuestProto->questRewardExtra.scriptID, pPlayer, pQuestProto,
			std::string_view(pQuestProto->questRewardExtra.scriptArgs));
	}

	OnQuestSubmit(pPlayer, pQuestLog);
	pQuestStorage->RemoveQuestLog(pQuestLog);
	pQuestStorage->AddToFinishedQuest(pQuestProto);
	auto questTypeID = pQuestProto->questTypeID;
	delete pQuestLog;

	RefreshQuestWatchStatus(pPlayer, questTypeID);
	pQuestStorage->SetQuestStatusDirty(questTypeID);

	return CommonSuccess;
}

GErrorCode QuestMgr::HandleEnterQuestGuide(Player* pPlayer, LocatableObject* pLObj, uint32 questTypeID, uint32 questUniqueKey)
{
	if (!pPlayer->IsInWorld()) {
		return InvalidRequest;
	}
	auto pQuestProto = GetDBEntry<QuestPrototype>(questTypeID);
	if (pQuestProto == NULL || !pQuestProto->questFlags.isStoryMode) {
		return InvalidRequest;
	}
	auto errCode = IsQuestObjInstMatchable(pPlayer, pLObj, pQuestProto->questGuider);
	if (errCode != CommonSuccess) {
		return errCode;
	}

	if (!pQuestProto->questFlags.isEnterSceneAeap) {
		auto pQuestLog = pPlayer->GetQuestStorage()->GetQuestLogByKey(questUniqueKey);
		if (pQuestLog == NULL) {
			return InvalidRequest;
		}
		if (pQuestLog->IsQuestFinished()) {
			return InvalidRequest;
		}
	}

	NetBuffer tpArgs;
	tpArgs << questTypeID << pPlayer->GetMapType();
	pPlayer->TeleportTo(
		MakeInstGuid((u16)MapInfo::Type::Story, pPlayer->GetMapId()),
		{pPlayer->GetPosition(), pPlayer->GetOrientation()},
		TeleportType::SwitchMap, 0, tpArgs.CastBufferStringView());

	return CommonSuccess;
}

void QuestMgr::InitAllQuestWatchStatus(Player* pPlayer)
{
	auto pQuestStorage = pPlayer->GetQuestStorage();
	for (auto pQuestProto : m_allWatchStatusQuests) {
		auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
		pQuestStorage->PushQuestWatchStatus(pQuestProto, questStatus);
	}
}

void QuestMgr::RefreshQuestWatchStatus(Player* pPlayer, uint32 questTypeID)
{
	auto pQuestProto = GetDBEntry<QuestPrototype>(questTypeID);
	if (pQuestProto != NULL && pQuestProto->questFlags.isWatchStatus) {
		auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
		pPlayer->GetQuestStorage()->PushQuestWatchStatus(pQuestProto, questStatus);
	}
}

void QuestMgr::RefreshQuestWatchStatus4LevelDirty(Player* pPlayer)
{
	auto pQuestStorage = pPlayer->GetQuestStorage();
	for (auto pQuestProto : m_allWatchStatusQuests4LvReq) {
		auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
		pQuestStorage->PushQuestWatchStatus(pQuestProto, questStatus);
	}
}

void QuestMgr::RefreshQuestWatchStatus4QuestStatusDirty(Player* pPlayer, uint32 questTypeID)
{
	auto itr = m_allWatchStatusQuests4QuestReq.find(questTypeID);
	if (itr != m_allWatchStatusQuests4QuestReq.end()) {
		auto pQuestStorage = pPlayer->GetQuestStorage();
		for (auto pQuestProto : itr->second) {
			auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
			pQuestStorage->PushQuestWatchStatus(pQuestProto, questStatus);
		}
	}
}

void QuestMgr::RefreshQuestWatchStatus4ChequeValueDirty(Player* pPlayer, uint32 chequeType)
{
	auto itr = m_allWatchStatusQuests4ChequeReq.find(chequeType);
	if (itr != m_allWatchStatusQuests4ChequeReq.end()) {
		auto pQuestStorage = pPlayer->GetQuestStorage();
		for (auto pQuestProto : itr->second) {
			auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
			pQuestStorage->PushQuestWatchStatus(pQuestProto, questStatus);
		}
	}
}

void QuestMgr::RefreshQuestWatchStatus4ItemCountDirty(Player* pPlayer, uint32 itemTypeID)
{
	auto itr = m_allWatchStatusQuests4ItemReq.find(itemTypeID);
	if (itr != m_allWatchStatusQuests4ItemReq.end()) {
		auto pQuestStorage = pPlayer->GetQuestStorage();
		for (auto pQuestProto : itr->second) {
			auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
			pQuestStorage->PushQuestWatchStatus(pQuestProto, questStatus);
		}
	}
}

void QuestMgr::SendObjCareNoWatchStatusQuests(Player* pPlayer, LocatableObject* pLObj)
{
	size_t n = 0;
	std::pair<QuestObjType, uint32> objInfos[2];
	switch (pLObj->GetObjectType()) {
		case TYPE_CREATURE: {
			auto pCreature = static_cast<Creature*>(pLObj);
			objInfos[n++] = { QuestObjType::NPC, pCreature->GetSpawnId() };
			objInfos[n++] = { QuestObjType::NPCPt, pCreature->GetEntry() };
			break;
		}
		case TYPE_STATICOBJECT: {
			auto pSObj = static_cast<StaticObject*>(pLObj);
			objInfos[n++] = { QuestObjType::SObj, pSObj->GetSpawnId() };
			objInfos[n++] = { QuestObjType::SObjPt, pSObj->GetEntry() };
			break;
		}
	}

	NetPacket pack(SMSG_QUERY_QUEST_GIVER_RESP);
	for (size_t i = 0; i < n; ++i) {
		auto objInfo = objInfos[i];
		auto& allObjQuests = m_allObjCareNoWatchStatusQuests[(int)objInfo.first];
		auto itr = allObjQuests.find(objInfo.second);
		if (itr != allObjQuests.end()) {
			for (auto pQuestProto : itr->second) {
				auto questStatus = CalcQuestStatus(pPlayer, pQuestProto);
				if (IsCareQuestStatus(questStatus)) {
					pack << pQuestProto->questTypeID << (int8)questStatus;
				}
			}
		}
	}
	pPlayer->SendPacket(pack);
}

QUEST_STATUS QuestMgr::CalcQuestStatus(Player* pPlayer, const QuestPrototype* pQuestProto)
{
	auto pQuestStorage = pPlayer->GetQuestStorage();
	if (pQuestStorage->GetQuestLogByEntry(pQuestProto->questTypeID)) {
		return QMGR_QUEST_NOT_FINISHED;
	}
	if (pQuestStorage->IsQuestFinished(pQuestProto)) {
		return QMGR_QUEST_FINISHED;
	}
	if (!IsCareerMatchable(pPlayer, pQuestProto->questReqCareer)) {
		return QMGR_QUEST_NOT_AVAILABLE;
	}
	if (!IsGenderMatchable(pPlayer, pQuestProto->questReqGender)) {
		return QMGR_QUEST_NOT_AVAILABLE;
	}
	if (!IsReqQuestsReached(pQuestProto, pQuestStorage)) {
		return QMGR_QUEST_NOT_AVAILABLE;
	}
	for (auto questReqCheque : pQuestProto->questReqCheques) {
		if (!pPlayer->IsChequeEnough(ChequeType(
			questReqCheque.chequeType), questReqCheque.chequeValue)) {
			return QMGR_QUEST_NOT_AVAILABLE;
		}
	}
	auto pItemStorage = pPlayer->GetItemStorage();
	for (auto questReqItem : pQuestProto->questReqItems) {
		if (IsCareerGenderMatchable(pPlayer, questReqItem)) {
			if (!pItemStorage->IsItemEnough(
				questReqItem.itemTypeID, questReqItem.itemCount)) {
				return QMGR_QUEST_NOT_AVAILABLE;
			}
		}
	}
	if (pQuestProto->questReqExtra.scriptID != 0) {
		auto questStatus = CalcQuestStatus4Script(pPlayer, pQuestProto);
		if (questStatus != QMGR_QUEST_AVAILABLE) {
			return questStatus;
		}
	}
	if (pQuestProto->questReqMaxLv != 0 &&
		pQuestProto->questReqMaxLv < pPlayer->GetLevel()) {
		return QMGR_QUEST_NOT_AVAILABLE;
	}
	if (pQuestProto->questReqMinLv != 0 &&
		pQuestProto->questReqMinLv > pPlayer->GetLevel()) {
		return QMGR_QUEST_AVAILABLELOW_LEVEL;
	}
	return QMGR_QUEST_AVAILABLE;
}

QUEST_STATUS QuestMgr::CalcQuestStatus4Script(Player* pPlayer, const QuestPrototype* pQuestProto)
{
	auto L = pPlayer->GetMapInstance()->L;
	const auto& scriptFile = GetScriptFileById(pQuestProto->questReqExtra.scriptID);
	if (!scriptFile.empty() && sLuaMgr.DoFile(L, scriptFile)) {
		return LuaFunc(L, "CalcQuestStatus").Call<QUEST_STATUS>(pPlayer,
			pQuestProto, std::string_view(pQuestProto->questReqExtra.scriptArgs));
	}
	return QMGR_QUEST_AVAILABLE;
}

void QuestMgr::AcceptQuest4Script(Player* pPlayer, const QuestPrototype* pQuestProto)
{
	auto L = pPlayer->GetMapInstance()->L;
	const auto& scriptFile = GetScriptFileById(pQuestProto->questReqExtra.scriptID);
	if (!scriptFile.empty() && sLuaMgr.DoFile(L, scriptFile)) {
		LuaFunc(L, "AcceptQuest").Call<void>(pPlayer,
			pQuestProto, std::string_view(pQuestProto->questReqExtra.scriptArgs));
	}
}

void QuestMgr::CostQuestReqCheques(Player* pPlayer,
	const QuestPrototype* pQuestProto, CHEQUE_FLOW_TYPE flowType)
{
	for (auto questReqCheque : pQuestProto->questReqCheques) {
		if (questReqCheque.isCost) {
			pPlayer->CostCheque(ChequeType(
				questReqCheque.chequeType), questReqCheque.chequeValue,
				flowType, {pQuestProto->questTypeID});
		}
	}
}

void QuestMgr::DestroyQuestReqItems(Player* pPlayer,
	const QuestPrototype* pQuestProto, ITEM_FLOW_TYPE flowType)
{
	auto pItemStorage = pPlayer->GetItemStorage();
	for (auto questReqItem : pQuestProto->questReqItems) {
		if (questReqItem.isDestroy &&
			IsCareerGenderMatchable(pPlayer, questReqItem)) {
			pItemStorage->RemoveItemAmount(
				questReqItem.itemTypeID, questReqItem.itemCount,
				flowType, {pQuestProto->questTypeID});
		}
	}
}

void QuestMgr::RefundQuestReqCheques(Player* pPlayer,
	const QuestPrototype* pQuestProto, CHEQUE_FLOW_TYPE flowType)
{
	for (auto questReqCheque : pQuestProto->questReqCheques) {
		if (questReqCheque.isCost && questReqCheque.isRefund) {
			pPlayer->GainCheque(ChequeType(
				questReqCheque.chequeType), questReqCheque.chequeValue,
				flowType, {pQuestProto->questTypeID});
		}
	}
}

void QuestMgr::RefundQuestReqItems(Player* pPlayer,
	const QuestPrototype* pQuestProto, ITEM_FLOW_TYPE flowType)
{
	std::vector<inst_item_prop> itemProps;
	for (auto questReqItem : pQuestProto->questReqItems) {
		if (questReqItem.isDestroy &&questReqItem.isRefund &&
			IsCareerGenderMatchable(pPlayer, questReqItem)) {
			itemProps.push_back({
				questReqItem.itemTypeID, questReqItem.itemCount,
				questReqItem.isBinding ? 1 : 0u});
		}
	}
	pPlayer->GetItemStorage()->CreateAddItemsMail(NULL, itemProps.data(),
		itemProps.size(), flowType, {pQuestProto->questTypeID});
}

void QuestMgr::ReceiveQuestInitCheques(Player* pPlayer,
	const QuestPrototype* pQuestProto, CHEQUE_FLOW_TYPE flowType)
{
	for (auto questInitCheque : pQuestProto->questInitCheques) {
		pPlayer->GainCheque(ChequeType(
			questInitCheque.chequeType), questInitCheque.chequeValue,
			flowType, {pQuestProto->questTypeID});
	}
}

void QuestMgr::ReceiveQuestInitItems(Player* pPlayer,
	const QuestPrototype* pQuestProto, ITEM_FLOW_TYPE flowType)
{
	std::vector<inst_item_prop> itemProps;
	for (auto questInitItem : pQuestProto->questInitItems) {
		if (IsCareerGenderMatchable(pPlayer, questInitItem)) {
			itemProps.push_back({
				questInitItem.itemTypeID, questInitItem.itemCount,
				questInitItem.isBinding ? 1 : 0u});
		}
	}
	pPlayer->GetItemStorage()->CreateAddItemsMail(NULL, itemProps.data(),
		itemProps.size(), flowType, {pQuestProto->questTypeID});
}

void QuestMgr::RetrieveQuestInitCheques(Player* pPlayer,
	const QuestPrototype* pQuestProto, CHEQUE_FLOW_TYPE flowType)
{
	for (auto questInitCheque : pQuestProto->questInitCheques) {
		if (questInitCheque.isRetrieve) {
			pPlayer->CostCheque(ChequeType(
				questInitCheque.chequeType), questInitCheque.chequeValue,
				flowType, {pQuestProto->questTypeID});
		}
	}
}

void QuestMgr::RetrieveQuestInitItems(Player* pPlayer,
	const QuestPrototype* pQuestProto, ITEM_FLOW_TYPE flowType)
{
	auto pItemStorage = pPlayer->GetItemStorage();
	for (auto questInitItem : pQuestProto->questInitItems) {
		if (questInitItem.isRetrieve &&
			IsCareerGenderMatchable(pPlayer, questInitItem)) {
			pItemStorage->RemoveItemAmount(
				questInitItem.itemTypeID, questInitItem.itemCount,
				flowType, {pQuestProto->questTypeID});
		}
	}
}

void QuestMgr::ReceiveQuestRewardCheques(Player* pPlayer,
	const QuestPrototype* pQuestProto, CHEQUE_FLOW_TYPE flowType)
{
	for (auto questRewardCheque : pQuestProto->questRewardCheques) {
		pPlayer->GainCheque(ChequeType(
			questRewardCheque.chequeType), questRewardCheque.chequeValue,
			flowType, {pQuestProto->questTypeID});
	}
}

void QuestMgr::ReceiveQuestRewardItems(Player* pPlayer,
	const QuestPrototype* pQuestProto, ITEM_FLOW_TYPE flowType)
{
	std::vector<inst_item_prop> itemProps;
	for (auto questRewardItem : pQuestProto->questRewardItems) {
		if (IsCareerGenderMatchable(pPlayer, questRewardItem)) {
			itemProps.push_back({
				questRewardItem.itemTypeID, questRewardItem.itemCount,
				questRewardItem.isBinding ? 1 : 0u});
		}
	}
	pPlayer->GetItemStorage()->CreateAddItemsMail(NULL, itemProps.data(),
		itemProps.size(), flowType, {pQuestProto->questTypeID});
}

void QuestMgr::OnQuestAccept(Player* pPlayer, QuestLog* pQuestLog)
{
	RunQuestScript4Event(pPlayer, pQuestLog, QuestWhenType::Accept);
	pPlayer->ObjectHookEvent_OnPlayerChangeQuestStatus(pQuestLog, QuestWhenType::Accept);
	pPlayer->ApplyQuestVisibleCreature(pQuestLog, QuestWhenType::Accept);

	pQuestLog->PostInit();
	pQuestLog->SendNewQuestInstance();
}

void QuestMgr::OnQuestFinish(Player* pPlayer, QuestLog* pQuestLog)
{
	RunQuestScript4Event(pPlayer, pQuestLog, QuestWhenType::Finish);
	pPlayer->ObjectHookEvent_OnPlayerChangeQuestStatus(pQuestLog, QuestWhenType::Finish);
	pPlayer->ApplyQuestVisibleCreature(pQuestLog, QuestWhenType::Finish);

	NetPacket pack(SMSG_QUEST_FINISH);
	pack << pQuestLog->GetQuestGuid();
	pPlayer->SendPacket(pack);
}

void QuestMgr::OnQuestFailed(Player* pPlayer, QuestLog* pQuestLog)
{
	RunQuestScript4Event(pPlayer, pQuestLog, QuestWhenType::Failed);
	pPlayer->ObjectHookEvent_OnPlayerChangeQuestStatus(pQuestLog, QuestWhenType::Failed);
	pPlayer->ApplyQuestVisibleCreature(pQuestLog, QuestWhenType::Failed);

	NetPacket pack(SMSG_QUEST_FAILED);
	pack << pQuestLog->GetQuestGuid();
	pPlayer->SendPacket(pack);
}

void QuestMgr::OnQuestCancel(Player* pPlayer, QuestLog* pQuestLog)
{
	RunQuestScript4Event(pPlayer, pQuestLog, QuestWhenType::Cancel);
	pPlayer->ObjectHookEvent_OnPlayerChangeQuestStatus(pQuestLog, QuestWhenType::Cancel);
	pPlayer->ApplyQuestVisibleCreature(pQuestLog, QuestWhenType::Cancel);

	NetPacket pack(SMSG_REMOVE_QUEST);
	pack << pQuestLog->GetQuestGuid();
	pPlayer->SendPacket(pack);
}

void QuestMgr::OnQuestSubmit(Player* pPlayer, QuestLog* pQuestLog)
{
	RunQuestScript4Event(pPlayer, pQuestLog, QuestWhenType::Submit);
	pPlayer->ObjectHookEvent_OnPlayerChangeQuestStatus(pQuestLog, QuestWhenType::Submit);
	pPlayer->ApplyQuestVisibleCreature(pQuestLog, QuestWhenType::Submit);

	NetPacket pack(SMSG_SUBMIT_QUEST);
	pack << pQuestLog->GetQuestGuid();
	pPlayer->SendPacket(pack);
}

void QuestMgr::RunQuestScript4Event(Player* pPlayer, QuestLog* pQuestLog, QuestWhenType type)
{
	auto& questScript = pQuestLog->GetQuestProto()->questScripts[(int)type];
	if (questScript.scriptID != 0) {
		RunScriptFileById(pPlayer->GetMapInstance()->L, questScript.scriptID,
			pPlayer, pQuestLog, std::string_view(questScript.scriptArgs));
	}
}

GErrorCode QuestMgr::IsQuestObjInstMatchable(Player* pPlayer, LocatableObject* pLObj, const QuestObjInst& questObjInst)
{
	switch (QuestObjType(questObjInst.objType)) {
	case QuestObjType::Guide: {
		if (!pPlayer->IsInGuideArea(questObjInst.objID, QUEST_INTERACTIVE_DIST_MAX)) {
			return DistanceTooFar;
		}
		return CommonSuccess;
	}
	case QuestObjType::NPC: {
		if (pLObj == NULL) {
			return TargetInvalid;
		}
		if (!pLObj->IsType(TYPE_CREATURE)) {
			return TargetInvalid;
		}
		if (((Creature*)pLObj)->GetSpawnId() != questObjInst.objID) {
			return TargetInvalid;
		}
		if (pPlayer->GetDistanceSq(pLObj) > SQ(QUEST_INTERACTIVE_DIST_MAX)) {
			return DistanceTooFar;
		}
		return CommonSuccess;
	}
	case QuestObjType::SObj: {
		if (pLObj == NULL) {
			return TargetInvalid;
		}
		if (!pLObj->IsType(TYPE_STATICOBJECT)) {
			return TargetInvalid;
		}
		if (((StaticObject*)pLObj)->GetSpawnId() != questObjInst.objID) {
			return TargetInvalid;
		}
		if (pPlayer->GetDistanceSq(pLObj) > SQ(QUEST_INTERACTIVE_DIST_MAX)) {
			return DistanceTooFar;
		}
		return CommonSuccess;
	}
	case QuestObjType::NPCPt: {
		if (pLObj == NULL) {
			return TargetInvalid;
		}
		if (!pLObj->IsType(TYPE_CREATURE)) {
			return TargetInvalid;
		}
		if (((Creature*)pLObj)->GetEntry() != questObjInst.objID) {
			return TargetInvalid;
		}
		if (pPlayer->GetDistanceSq(pLObj) > SQ(QUEST_INTERACTIVE_DIST_MAX)) {
			return DistanceTooFar;
		}
		return CommonSuccess;
	}
	case QuestObjType::SObjPt: {
		if (pLObj == NULL) {
			return TargetInvalid;
		}
		if (!pLObj->IsType(TYPE_STATICOBJECT)) {
			return TargetInvalid;
		}
		if (((StaticObject*)pLObj)->GetEntry() != questObjInst.objID) {
			return TargetInvalid;
		}
		if (pPlayer->GetDistanceSq(pLObj) > SQ(QUEST_INTERACTIVE_DIST_MAX)) {
			return DistanceTooFar;
		}
		return CommonSuccess;
	}
	default:
		return CommonSuccess;
	}
}
